package com.genexus.search;

import java.io.*;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.lucene.analysis.*;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;


public class Searcher
{
	private Analyzer m_analyzer = Indexer.CreateAnalyzer();
	private static Searcher m_instance = new Searcher();
	private IndexSearcher m_searcher;
	public Searcher(){}
	public static Searcher getInstance(){return m_instance;}

	private String TranslateQuery(String query, Object context)
	{
		/*try{
						Pattern p;
						try {
							String rex = "\\([^\\(\\)]*\\)";
							p = Pattern.compile(rex, Pattern.MULTILINE);
						} catch (PatternSyntaxException e) {
							return query;
						}
						Matcher m = p.matcher(query);
						while (m.find()) {
							String value = m.group(0);
							for (int i = 1; i <= m.groupCount(); i++) {
								if (m.group(i) != null) {
									String match = m.group(i);
									String translatedStr = context.getHttpContext().getMessage(match.substring(1,match.length() - 2));
									query = GXutil.strReplace(query, match,translatedStr);
								}
							}

						}
						return query;
					}catch (Exception e) {
						return query;
					}*/
		return query;
	}
	public SearchResult search(String query, int maxresults)
	{
		return search(query, maxresults, 0, null);
	}

	public SearchResult search(String query, int maxresults, Object context)
	{
		return search(query, maxresults, 0, null);
	}

	public SearchResult search(String query,  int itemsPerPage, int pageNumber, Object context)
	{
		if (query == null || query.length() == 0)
			return SearchResult.getEmpty();

		if (context != null)
		{
			query = TranslateQuery(query, context);
		}
		IndexSearcher searcher = getSearcher();
		if (searcher == null)
			return SearchResult.getEmpty();

		QueryParser qp = new QueryParser(IndexRecord.CONTENTFIELD, m_analyzer);
		qp.setDefaultOperator(QueryParser.Operator.AND);
		qp.setAllowLeadingWildcard(true);
		try
		{
			Query q = qp.parse(query);

			Date t1 = new Date();
			searcher.setDefaultFieldSortScoring(true, false);
			Hits hits = searcher.search(q, Sort.RELEVANCE);

			Date t2 = new Date();
			//searcher.close();
                        
			return new SearchResult(hits, itemsPerPage, pageNumber, t2.getTime()-t1.getTime());
		}catch(Exception ex){
			ex.printStackTrace();
			String message = "";
			for (StackTraceElement stackTraceElement : ex.getStackTrace()) {
				message += "Exception thrown from " + stackTraceElement.getMethodName() + " in class " +
						stackTraceElement.getClassName() + " on line number " + stackTraceElement.getLineNumber() +
						" of file " + stackTraceElement.getFileName() + "\r\n";

			}
			System.out.println(message);
			return null;
		}
	}

	public void Close()  {
		if (m_searcher != null){
			try {
				m_searcher.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			m_searcher = null;
		}
	}

	private IndexSearcher getSearcher()
	{
		try
		{
			if (m_searcher == null){
				m_searcher = new IndexSearcher(Settings.getInstance().getIndexFolder());
			}
			return m_searcher;
		}
		catch(Exception ex)
		{
			return null;
		}
	}

	public void setMatchAndFound(Document doc, String query){

		try{
			QueryParser qp = new QueryParser(IndexRecord.CONTENTFIELD, m_analyzer);
			qp.setDefaultOperator(QueryParser.Operator.AND);
			qp.setAllowLeadingWildcard(true);

			Query q = qp.parse(query);

			/* Here we must extract from search query all different terms of search */

			String terms = getQueryTerms(q);

			String[] listTerms = terms.split(" ");

			List<String> list = new ArrayList<String>();

			for (String listTerm : listTerms) {
				if (!list.contains(listTerm)) {
					list.add(listTerm);
				}
			}
                        
   

			String htmlPreview = customHTMLPreview(doc.getField(IndexRecord.TITLEFIELD).stringValue(), query);
                        
			String match = parseHtmlPreview(htmlPreview);

			/* Here we store the matched words in a list of strings */
			String[] matches = match.split(" ");
                        

			for (int i = 0; i < matches.length; i++){
				matches[i] = matches[i].trim().toLowerCase();
			}

			List<String> listMatch = Arrays.asList(matches);

			String found, matchWord, termLower;
			boolean done = true;

			/*
			* Now we check for each word in match field, if in search query
			* exists exactly this word.
			*/
                        
			for (String aListMatch : listMatch) {
				matchWord = aListMatch.trim().toLowerCase();

				if (!list.contains(matchWord))
					done = false;
			}
                        
                        
                        
                        for (String term : list){
                            
                            termLower  = term.trim().toLowerCase();
                            
                            if (!listMatch.contains(termLower))
                                done = false;
                            
                        }
                        
                        

			if (done){ // Step 6

				doc.removeField(IndexRecord.FOUNDFIELD);
				doc.removeField(IndexRecord.DONEFIELD);
				doc.add(new Field(IndexRecord.DONEFIELD, "false", Field.Store.YES, Field.Index.UN_TOKENIZED));
				doc.add(new Field(IndexRecord.FOUNDFIELD, match, Field.Store.YES, Field.Index.TOKENIZED));
			}
			doc.removeField(IndexRecord.MATCHFIELD);
			doc.add(new Field(IndexRecord.MATCHFIELD, match, Field.Store.YES, Field.Index.TOKENIZED));

			IndexSearcher searcher = getSearcher();
			QueryParser qpAux = new QueryParser(IndexRecord.CONTENTFIELD, m_analyzer);
			qpAux.setDefaultOperator(QueryParser.Operator.AND);
			qpAux.setAllowLeadingWildcard(true);

			ResultsMap r = ResultsMap.getInstance();

			String searchTermSimilarity = getSearchTerms(q);
			String[] listSearchTerms = searchTermSimilarity.split(" ");

			List<String> listSearchTermsSimilarity = new ArrayList<String>();
			for (String listSearchTerm : listSearchTerms) {
				if (!listSearchTermsSimilarity.contains(listSearchTerm)) {
					listSearchTermsSimilarity.add(listSearchTerm);
				}
			}
                        
                        /*System.out.println("Similarity");
                        
                        for (String l : listSearchTermsSimilarity){
                            System.out.println(l);
                        }*/


			for (String st : listSearchTermsSimilarity) {
				if (!st.equals("")){
					String fuzzyQuery = "(" + st + ")";
					qpAux.setAllowLeadingWildcard(true);
					Query newQuery = qpAux.parse(fuzzyQuery);

					Hits newHits;
					if (r.getResult(fuzzyQuery) == null){
						newHits = searcher.search(newQuery, Sort.RELEVANCE);
						r.addResult(fuzzyQuery, newHits);
					}
					else
						newHits = r.getResult(fuzzyQuery);

					Iterator iterator = newHits.iterator();
					while (iterator.hasNext()){
                                            
						Hit hit = (Hit)iterator.next();
						Document newDoc = hit.getDocument(); // newDoc it´s the document with of the new search

						if (doc.getField(IndexRecord.URIFIELD).stringValue().equals(newDoc.getField(IndexRecord.URIFIELD).stringValue())){
                                                    
                                                    	if (!doc.getField(IndexRecord.DONEFIELD).stringValue().equals("true")){
								found = doc.getField(IndexRecord.FOUNDFIELD).stringValue();
								match = doc.getField(IndexRecord.MATCHFIELD).stringValue();

                                                                String searchWord = getSearchTermFound(list, st);
                                                                String newFound = "";
                                                                
								if (found.trim().toLowerCase().equals("empty")){
									newFound = searchWord + " ";
								}else{
									newFound = found + searchWord + " ";
								}

                                                                doc.removeField(IndexRecord.FOUNDFIELD);
								doc.add(new Field(IndexRecord.FOUNDFIELD, newFound, Field.Store.YES, Field.Index.TOKENIZED));
								break;
							}
						}
					}
				}
			}

                        
                        String f = doc.getField(IndexRecord.FOUNDFIELD).stringValue();
                        
                        doc.removeField(IndexRecord.FOUNDFIELD);
                        doc.add(new Field(IndexRecord.FOUNDFIELD, removeDuplicates(f), Field.Store.YES, Field.Index.TOKENIZED));
                        
			if (!doc.getField(IndexRecord.DONEFIELD).stringValue().equals("true")){
				found = doc.getField(IndexRecord.FOUNDFIELD).stringValue();
				if (found.trim().toLowerCase().equals("empty")){
					doc.removeField(IndexRecord.FOUNDFIELD);
					doc.add(new Field(IndexRecord.FOUNDFIELD, " ", Field.Store.YES, Field.Index.TOKENIZED));
				}
			}

		}catch(Exception ex){
			ex.printStackTrace();
			String message = "";
			for (StackTraceElement stackTraceElement : ex.getStackTrace()) {
				message += "Exception thrown from " + stackTraceElement.getMethodName() + " in class " +
						stackTraceElement.getClassName() + " on line number " + stackTraceElement.getLineNumber() +
						" of file " + stackTraceElement.getFileName() + "\r\n";

			}
		}
	}

        
        private String removeDuplicates(String found){
            
            String[] founds = found.split(" ");
            
            List<String> foundList = Arrays.asList(founds);
            
            ArrayList<String> res = new ArrayList<String>();
            
            for (String f : foundList){
                
                if (!res.contains(f.toLowerCase().trim())){
                    res.add(f.toLowerCase().trim());
                }
                
            }
            
            
            String result = "";
            
            for (String r : res){
                result += " " + r;
            }
            
            
            return result;
        }
        
	private String getSearchTermFound(List<String> list, String searchTerm){


            for (String st : list) {
                    if (searchTerm.toLowerCase().startsWith(st.toLowerCase()))
                            return st;
            }
			
            
            return "";
	}

	private String getSearchTerms(Query q){

		String result = "";
		if (q instanceof BooleanQuery){
			BooleanQuery bq = (BooleanQuery)q;

			BooleanClause[] bcs = bq.getClauses();

			for (BooleanClause bc : bcs) {
				result += getSearchTerms(bc.getQuery());
			}

		}else if (q instanceof FuzzyQuery){
			FuzzyQuery fq = (FuzzyQuery)q;

			result += fq.getTerm().text() + "~" + fq.getMinSimilarity() + " ";

		}else if (q instanceof WildcardQuery){
			WildcardQuery wcq = (WildcardQuery)q;
			//result += wcq.getTerm().text() + " ";
			result += "";
		}

		return result;
	}

	public void setMatch(Document doc, String match){
		doc.removeField(IndexRecord.MATCHFIELD);
		doc.add(new Field(IndexRecord.MATCHFIELD, match, Field.Store.YES, Field.Index.TOKENIZED));
	}

	private String parseHtmlPreview(String htmlPreview){

		int index = 0, newIndex = 0, newEnd;
		String match = "", aux = htmlPreview;

		/* Now we parse the htmlPreview string */
		/* The htmlPreview string has the matched words enclosed by <b> and </b> */
		while (index < htmlPreview.length()){
			newIndex = aux.indexOf("<b>", newIndex);
			if (newIndex > 0){
				newEnd = aux.indexOf("</b>");
				String sub = aux.substring(newIndex+3, newEnd);
				match += sub + " ";
				index = newEnd+4;
				newIndex = 0;
				aux = aux.substring(newEnd+4);
			}else{
				break;
			}
		}

		return match;
	}

	private String getQueryTerms(Query q){

		String result = "";

		if (q instanceof BooleanQuery){
			BooleanQuery bq = (BooleanQuery)q;

			BooleanClause[] bcs = bq.getClauses();

			for (BooleanClause bc : bcs) {
				result += getQueryTerms(bc.getQuery());
			}

		}else if (q instanceof FuzzyQuery){
			FuzzyQuery fq = (FuzzyQuery)q;

			result += fq.getTerm().text() + " ";

		}else if (q instanceof TermQuery){
			TermQuery tq = (TermQuery)q;

			result += tq.getTerm().text() + " ";

		}else if (q instanceof PhraseQuery){
			PhraseQuery phq = (PhraseQuery)q;

			Term[] terms = phq.getTerms();

			for (Term term : terms) {
				result += term.text() + " ";
			}
		}else if (q instanceof WildcardQuery){
			WildcardQuery wcq = (WildcardQuery)q;
			//result += wcq.getTerm().text() + " ";
			result += "";
		}
		return result;
	}

	private String customHTMLPreview(String obj, String query){
		QueriesMap qm = QueriesMap.getInstance();
		IndexReader reader = null;
		String text = new JTidyHTMLHandler().getTextFromString(obj);
		try{
			if (!query.equals("") && !text.equals("")){
				QueryParser qp = new QueryParser(IndexRecord.CONTENTFIELD,
						Indexer.CreateAnalyzer());
				qp.setDefaultOperator(QueryParser.Operator.AND);
				qp.setAllowLeadingWildcard(true);

				Query unReWrittenQuery = qp.parse(query);
				Query q = unReWrittenQuery;

				try
				{
					if (reader == null)
					{
						reader = IndexReader.open(Settings.getInstance().getIndexFolder());
					}
					if (qm.getQuery(query)!=null)
					{
						q = qm.getQuery(query);
					}else
					{

						q = unReWrittenQuery.rewrite(reader);//required to expand search terms (for the usage of highlighting with wildcards)

						qm.addQuery(query, q);
					}
				}catch(Exception ignored){}

				QueryScorer scorer = new QueryScorer(q);

				SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<b>","</b>");
				Highlighter highlighter = new Highlighter(formatter, scorer);
				Fragmenter fragmenter = new SimpleFragmenter(500);
				highlighter.setTextFragmenter(fragmenter);

				TokenStream tokenStream = Indexer.CreateAnalyzer().tokenStream(IndexRecord.CONTENTFIELD, new StringReader(text));

				String result = highlighter.getBestFragments(tokenStream, text, 1, "...");

				reader.close();

				return result;
			}else{
				return text;
			}
		}catch(Exception ex){
			System.out.println(ex.getMessage());
			ex.printStackTrace();
			return "";
		}
	}
}
